Skip to content

ci: add shfmt and shellcheck for shell scripts - #691

Merged
kolyshkin merged 8 commits into
containers:mainfrom
kolyshkin:ci-shell
Aug 26, 2026
Merged

ci: add shfmt and shellcheck for shell scripts#691
kolyshkin merged 8 commits into
containers:mainfrom
kolyshkin:ci-shell

Conversation

@kolyshkin

@kolyshkin kolyshkin commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Adds shfmt and shellcheck to CI, plus the fixes needed to make the tree clean under both.

Both run the way runc runs shfmt: a make target using a pinned container image, so a local run and a CI run cannot disagree, plus localshfmt / localshellcheck targets for whoever already has the tools installed. The CI jobs are then just make shfmt and make shellcheck. Pinning matters more for shellcheck than for shfmt, since what it reports moves between releases and ubuntu-latest is several versions behind.

hack/get_ci_vm.sh

Removed first, because it is dead. It sets up a Cirrus-CI VM in GCP, and conmon stopped using Cirrus in e0c56ec; its --setup path sources ./contrib/cirrus/lib.sh and runs ./contrib/cirrus/setup_environment.sh, both deleted in 42cecdf. shellcheck is what pointed this out, via the SC1091 on that source line.

shfmt

The .editorconfig is podman's file minus space_redirects, so redirections keep the shape they already have here. The [[shell]] section is what lets shfmt pick up hack/github-actions-setup, a shell script with a shebang and no extension; that section is an shfmt extension rather than something editorconfig itself understands. See podman-container-tools/podman#28785.

shfmt finds the files to format on its own, .bats files included, so no file list has to be maintained.

shellcheck

One commit per theme: quoting (SC2086), declare-and-assign (SC2155), the printf padding trick (SC2140, SC2183), moving a helper above its callers (SC2218), and directives for the places shellcheck cannot know the context (SC2016, SC1091, SC2034, SC2154). No check is disabled globally.

None of the findings was an actual bug. The one that needed care is get_conmon_journal_output(), where the word splitting was load-bearing: its level filter is either empty or -p <level>, which has to reach journalctl as two arguments, so it becomes an array rather than a quoted string.

Note

test/Makefile was reformatted in passing before this branch and lost the tabs in its clean recipe, which broke make -C test clean with missing separator. That is reverted here. It may be worth an .editorconfig section pinning Makefiles to tabs so it cannot happen again.

@kolyshkin kolyshkin changed the title ci: format shell scripts with shfmt ci: add shfmt and shellcheck for shell scripts Aug 26, 2026
@kolyshkin
kolyshkin marked this pull request as ready for review August 26, 2026 02:00

@jnovy jnovy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment thread .editorconfig Outdated
kolyshkin and others added 8 commits August 26, 2026 13:04
The script sets up a Cirrus-CI VM in GCP, and conmon stopped using
Cirrus in e0c56ec ("ci: replace Cirrus CI with GitHub Actions"). Its
--setup path sources ./contrib/cirrus/lib.sh and runs
./contrib/cirrus/setup_environment.sh, both of which were deleted in
42cecdf ("Cirrus: Remove disused scripts"), so that path cannot work at
all any more.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add an .editorconfig based on the one podman uses, reformat every shell
script in the tree with shfmt, and add a CI job that keeps it that way.

The .editorconfig is podman's file minus space_redirects, so redirections
keep the shape they already have here. The [[shell]] section is what lets
shfmt pick up hack/github-actions-setup, which is a shell script with a
shebang and no extension; that section is an shfmt extension rather than
something editorconfig itself understands. See
podman-container-tools/podman#28785.

shfmt itself runs the way runc runs it: a make target using a pinned
container image, so that a local run and a CI run cannot disagree about
formatting, plus a localshfmt target for whoever already has shfmt
installed. The CI job is then just "make shfmt".

shfmt finds the files to format on its own, .bats files included, so no
file list has to be maintained anywhere.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Found by shellcheck (SC2086). None of these were broken in practice,
since the paths involved never contain whitespace or glob characters, but
quoting them is what keeps that true.

get_conmon_journal_output() is the one place where the word splitting was
load-bearing: its level filter is either empty or "-p <level>", which has
to reach journalctl as two separate arguments. Quoting it as it stood
would have passed it as one, so it becomes an array instead.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Found by shellcheck (SC2155). "local x=$(cmd)" makes the exit status of
local, which is always zero, mask the exit status of cmd. None of these
four checked that status, so the bug is latent rather than real, but the
next person to add "set -e" or a status check would not get what they
expect.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Found by shellcheck (SC2140, SC2183). Both places build a 65535 character
line the same way, and both were sloppy about it:

The %*s conversion takes a width and a string, so "printf '%*s' 65535"
was relying on printf treating the missing second argument as empty.
Pass the empty string explicitly.

In 02-ctr-logs.bats the whole command was also a double quoted string
containing a bare "65535", which merely concatenated into the intended
text rather than quoting anything. Drop the inner quotes.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Found by shellcheck (SC2218). The function sat at the bottom of the file
while seven tests called it. bats sources the whole file before running
any test, so this worked, but shellcheck reads the file as an ordinary
script and cannot know that. Moving the definition next to the other
helpers is also just easier to read.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Five places where shellcheck is reading the code correctly but cannot
know the context, so they get a directive with the reason:

 - two single quoted strings (SC2016) hold script text meant to be
   expanded somewhere else, once in the container and once in a child
   shell that inherits an exported TEST_TMPDIR;
 - two files source /etc/os-release (SC1091), which shellcheck cannot
   follow because it is not part of the tree;
 - in test_helper.bash, status and output (SC2154) come from bats' run,
   and variables such as VALID_PATH (SC2034) are only referenced by the
   .bats files that load the helper.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Run it the same way as shfmt: a make target using a pinned container
image, a localshellcheck target for whoever has shellcheck installed, and
a CI job that is just "make shellcheck". Pinning matters more here than
it does for shfmt, since what shellcheck reports moves between releases
and ubuntu-latest is several versions behind.

The tree is clean as of the preceding commits.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kolyshkin

Copy link
Copy Markdown
Collaborator Author

rebased (conflicts in Makefile resolved)

@kolyshkin
kolyshkin merged commit c77ea56 into containers:main Aug 26, 2026
35 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants